package com.common.myspring.core;

import com.common.myspring.annotation.Component;
import com.common.myspring.annotation.Resource;
import com.common.myspring.annotation.aop.Aspect;
import com.common.myspring.annotation.aop.PointCut;
import com.common.myspring.function.AdviceInterface;
import com.common.myspring.util.AopUtil;
import com.common.myspring.util.BaseUtil;
import com.common.myspring.util.StringUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @title: ApplicationContext
 * @Author xu-xiang
 * @Date: 2023/8/26 14:25
 * @description: spring上下文
 */
@Slf4j
public class ApplicationContext {

    private static final Map<Class<?>, Object> clazzBeanMap = new HashMap<>();

    private static final Map<String, Object> nameBeanMap = new HashMap<>();

    private static final Map<Class<?>, Map<Method, List<AdviceInterface>>> adviceMap = new HashMap<>();

    private static final Map<Class<? extends Annotation>, AdviceInterface> adviceInitMap = new HashMap<>();

    /**
     * scanPath 扫描路径
     */
    private String scanPath;

    public ApplicationContext() {
    }

    public ApplicationContext(String scanPath) {
        this.scanPath = scanPath;
    }

    /**
     * 实例化路径下的所有的bean到容器
     */
    public void init(boolean isLazy) {
        //1. 扫描路径. 加载路径下的所有class
        Set<Class<?>> classes = BaseUtil.getClassSetWithPath(scanPath);

        //2. 将@component 的类.加载到容器
        synchronized (ApplicationContext.class) {
            try {
                for (Class<?> clazz : classes) {
                    this.doNewInstance(clazz);  //实例化bean
                }

                for (Class<?> clazz : classes) {
                    //扫描切入点
                    for (Method method : clazz.getDeclaredMethods()) {
                        adviceInitMap.forEach((anno, advice) -> {
                            if (method.isAnnotationPresent(anno)) {
                                List<AdviceInterface> adviceList = getAdvice(clazz, method);
                                adviceList.add(advice);
                            }
                        });
                    }
                }

                if (!isLazy && !clazzBeanMap.isEmpty()) {
                    this.initBean();
                }
            } catch (Exception e) {
                log.error("initialize Bean error");
                throw new RuntimeException(e);
            }
        }
    }

    private void doNewInstance(Class<?> clazz) throws Exception {
        Component component = clazz.getAnnotation(Component.class);
        if (component == null) {
            return;
        }

        //扫描注解
        this.scanAspect(clazz);

        Object bean = clazz.newInstance();
        clazzBeanMap.put(clazz, bean);

        // 将bean放入容器,默认为类名首字母小写
        String beanName = component.value().isEmpty() ?
                StringUtil.toLowerFirstLetter(clazz.getSimpleName()) : component.value();

        //todo 抛出具体类,bean名称重复
        if (nameBeanMap.containsKey(beanName)) {
            log.error(clazz.getName() + "_Bean名称[ " + beanName + " ]重复");
            throw new RuntimeException("bean name " + beanName + " repeat");
        }

        nameBeanMap.put(beanName, bean);
    }

    /**
     * 扫描切面类
     */
    private void scanAspect(Class<?> clazz) throws Exception {

        if (!clazz.isAnnotationPresent(Aspect.class)) {
            return;
        }

        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            PointCut annotation = method.getAnnotation(PointCut.class);
            if (annotation != null && annotation.pointCut() != null) {
                Object instance = clazz.newInstance(); //原始类
                AdviceInterface advice = (o, m, arg) -> method.invoke(instance, o, m, arg);
                Class<? extends Annotation> pointCut = annotation.pointCut();

                //所有带有上面注解pointCut的,全部加上advice增强
                adviceInitMap.put(pointCut, advice);
            }
        }
    }

    /**
     * bean初始化属性注入
     */
    private void initBean() throws IllegalAccessException {

        for (Map.Entry<Class<?>, Object> entry : clazzBeanMap.entrySet()) {
            Class<?> clazz = entry.getKey();
            Object bean = entry.getValue();

            //通过反射,DI
            for (Field field : clazz.getDeclaredFields()) {
                Resource annotation = field.getAnnotation(Resource.class);
                if (annotation == null) {
                    continue;
                }

                field.setAccessible(true);
                Object fieldObject = null;

                if (annotation.value().isEmpty()) {
                    //默认通过name去匹配
                    fieldObject = this.getBean(field.getName());

                    //名称匹配不到,则用类型匹配
                    if (fieldObject == null) {
                        fieldObject = this.getBean(field.getType());
                    }
                } else {
                    //通过指定的bean名称去匹配
                    fieldObject = nameBeanMap.get(annotation.value());
                }

                // 检查是否必须注入bean
                if (annotation.required() && fieldObject == null) {
                    log.error(clazz.getName() + "的属性[" + field.getName() + "]注入失败!没有找到对应的实例");
                    throw new NullPointerException("can`t inject bean with Null: " + clazz.getName() + "--" + field.getName());
                } else {
                    field.set(bean, adviceMap.containsKey(fieldObject.getClass()) ? AopUtil.getProxyInstance(fieldObject) : fieldObject);
                }

            }
        }
    }

    /**
     * 根据类型 获取一个bean对象
     */
    public <T> T getBean(Class<T> clazz) {
        List<T> beans = this.getBeansWithType(clazz);
        if (beans.isEmpty()) {
            return null;
        }

        if (beans.size() == 1) {
            T bean = beans.get(0);
            return adviceMap.containsKey(clazz) ? AopUtil.getProxyInstance(bean) : bean;
        }

        log.error("根据类型获取一个bean, 但是找到多个此类型的bean");
        throw new RuntimeException("get 1 bean [" + clazz.getSimpleName() + "] but we found " + beans.size());
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> getBeansWithType(Class<T> clazz) {
        List<T> beans = new ArrayList<>();

        for (Map.Entry<Class<?>, Object> entry : clazzBeanMap.entrySet()) {
            if (clazz.isAssignableFrom(entry.getKey())) {
                beans.add((T) entry.getValue());
            }
        }

        return beans;
    }

    /**
     * 根据bean名称获取bean对象
     */
    @SuppressWarnings("unchecked")
    public <T> T getBean(String name) {
        return (T) nameBeanMap.get(name);
    }

    public Map<String, Object> getBeanNameMap() {
        return nameBeanMap;
    }

    public Map<Class<?>, Object> getBeanClassMap() {
        return clazzBeanMap;
    }

    public static List<AdviceInterface> getAdvice(Class<?> clazz, Method method) {
        Map<Method, List<AdviceInterface>> methodMap = adviceMap.get(clazz);
        if (methodMap == null) {
            Map<Method, List<AdviceInterface>> methodListMap = new HashMap<>();
            ArrayList<AdviceInterface> adviceInterfaces = new ArrayList<>();
            methodListMap.put(method, adviceInterfaces);
            adviceMap.put(clazz, methodListMap);
            return adviceInterfaces;
        }

        for (Map.Entry<Method, List<AdviceInterface>> entry : methodMap.entrySet()) {
            Method k = entry.getKey();
            if (k.getName().equals(method.getName()) && Arrays.equals(k.getParameterTypes(), method.getParameterTypes())) {
                return entry.getValue();
            }
        }

        throw new RuntimeException("advice not exist");
    }

}
